package com.framework.spring;

import com.framework.spring.annotations.Component;
import com.framework.spring.domain.BeanDefinition;
import com.framework.spring.postprocessors.BeanPostProcessor;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author duanzhizhou
 * @date 2025/4/8
 */
public class ApplicationContext {
    
    Map<String, Object> ioc = new HashMap<>();
    
    Map<String, Object> earlyBeanMap = new HashMap<>();
    
    Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    
    List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
    
    private void init(String packName) throws Exception {
        List<BeanDefinition> definitions = scanPackage(packName).stream()
                .filter(c -> c.isAnnotationPresent(Component.class))
                .map(this::wraper)
                .collect(Collectors.toList());
        List<BeanDefinition> beanPostProcessorDefinitions = definitions.stream()
                .filter(bd -> BeanPostProcessor.class.isAssignableFrom(bd.getType())).collect(Collectors.toList());
        for (BeanDefinition definition : beanPostProcessorDefinitions) {
            beanPostProcessors.add((BeanPostProcessor) createBean(definition));
        }
        definitions.forEach(this::createBean);
        
    }
    
    private Object createBean(BeanDefinition beanDefinition) {
        if (ioc.containsKey(beanDefinition.getName())) {
            return ioc.get(beanDefinition.getName());
        }
        if (earlyBeanMap.containsKey(beanDefinition.getName())) {
            return earlyBeanMap.get(beanDefinition.getName());
        }
        return doCreateBean(beanDefinition);
    }
    
    private Object doCreateBean(BeanDefinition beanDefinition) {
        try {
            Object o = beanDefinition.getConstructor().newInstance();
            earlyBeanMap.put(beanDefinition.getName(), o);
            populateBean(o, beanDefinition);
            o = initializeBean(o, beanDefinition);
            earlyBeanMap.remove(beanDefinition.getName());
            ioc.put(beanDefinition.getName(), o);
            return o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private Object initializeBean(Object o, BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {
        
        for (BeanPostProcessor processor : beanPostProcessors) {
            o = processor.beforeInitialization(o, beanDefinition.getName());
        }
        
        if (beanDefinition.getPostContstruct() != null) {
            beanDefinition.getPostContstruct().invoke(o);
        }
        
        for (BeanPostProcessor processor : beanPostProcessors) {
            o = processor.afterInitialization(o, beanDefinition.getName());
        }
        return o;
    }
    
    private void populateBean(Object o, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field field : beanDefinition.getFields()) {
            field.setAccessible(true);
            Class<?> filedClass = field.getType();
            Object bean = getBean(filedClass);
            field.set(o, bean);
        }
    }
    
    private BeanDefinition wraper(Class<?> clazz) {
        BeanDefinition definition = new BeanDefinition(clazz);
        if (beanDefinitionMap.containsKey(definition.getName())) {
            throw new RuntimeException("重复定义的Bean: " + definition.getName());
        }
        beanDefinitionMap.put(definition.getName(), definition);
        return definition;
    }
    
    private List<Class<?>> scanPackage(String packName) throws URISyntaxException, IOException {
        URL resource = this.getClass().getClassLoader().getResource(packName.replace(".", File.separator));
        Path path = Paths.get(resource.toURI());
        List<Class<?>> classList = new ArrayList<>();
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String fullName = file.toString();
                if (fullName.endsWith(".class")) {
                    fullName = fullName.replace(File.separator, ".");
                    String className = fullName.substring(fullName.indexOf(packName), fullName.length() - ".class".length());
                    try {
                        if (!className.contains("annotations")) {
                            classList.add(Class.forName(className));
                        }
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return classList;
    }
    
    public ApplicationContext(String packName) throws Exception {
        init(packName);
    }
    
    public Object getBean(String name) {
        if (name == null || !beanDefinitionMap.containsKey(name)) {
            return null;
        }
        return createBean(beanDefinitionMap.get(name));
    }
    
    public <T> T getBean (Class<T> clazz) {
        String name = beanDefinitionMap.values().stream()
                .filter(bd -> clazz.isAssignableFrom(bd.getType()))
                .map(BeanDefinition::getName)
                .findFirst()
                .orElse(null);
        return (T) getBean(name);
    }
}
